using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace MKDS_Course_Editor.GCN.BMD
{
    class JNT1
    {
        public class Jnt1Header
        {
            public string tag; //'JNT1'
            public UInt32 sizeOfSection;
            public UInt16 count; //number of joints
            public UInt16 pad; //padding UInt16 (?)

            public UInt32 jntEntryOffset; //joints are stored at this place
            //offset relative to Jnt1Header start

            public UInt32 unknownOffset; //there are count UInt16's stored at this point,
            //always the numbers 0 to count - 1 (in that order).
            //perhaps an index-to-stringtable-index map?
            //offset relative to Jnt1Header start
            public UInt32 stringTableOffset; //names of joints
        }


        class JntEntry
        {
            public UInt16 unknown; //no idea how this works...always 0, 1 or 2
            //"matrix type" according to yaz0r - whatever this means ;-)
            //if this is 1 or 2, unknown2 and bounding box is 0.f
            //seems to be always 0 if a joint has direct non-joint children
            //(it's even "iff" - only joints with non-joint children have 0?)

            public byte unknown3; //0 or 1 (0xff in one single file) (inherit parent scale???)

            public byte pad; //always 0xff

            public float sx, sy, sz; //scale
            public short rx, ry, rz; //-32768 = -180 deg, 32768 = 180 deg
            public UInt16 pad2; //always 0xffff
            public float tx, ty, tz; //translation

            public float unknown2;
            public float[] bbMin = new float[3]; //bounding box (?)
            public float[] bbMax = new float[3]; //bounding box (?)
        }
        public class Frame
        {
            public float sx, sy, sz; //scale
            public float rx, ry, rz; //rotation (in degree)
            public OpenTK.Vector3 t = new OpenTK.Vector3(); //translation
            public string name;

            //TODO:
            //bounding box, float unknown2

            //experimentally
            public UInt16 unknown;
            public byte unknown3;
            public OpenTK.Vector3 bbMin = new OpenTK.Vector3(), bbMax = new OpenTK.Vector3();
        }

        public class Jnt1
        {
            public List<Frame> frames = new List<Frame>();

            //the Frames have to be converted to matrices
            //to be usable by gl. isMatrixValid stores
            //if a matrix represents a frame of if the
            //frame has changed since the matrix was
            //built (in animations for example)
            public List<LibNDSFormats.NSBMD.MTX44> matrices = new List<LibNDSFormats.NSBMD.MTX44>();
            public List<bool> isMatrixValid = new List<bool>(); //TODO: use this

            //TODO: unknown array
        }

        private static void readJnt1Header(EndianBinaryReader er, Jnt1Header h)
        {
            h.tag = er.ReadString(Encoding.ASCII, 4);
            h.sizeOfSection = er.ReadUInt32();
            h.count = er.ReadUInt16();
            h.pad = er.ReadUInt16();
            h.jntEntryOffset = er.ReadUInt32();
            h.unknownOffset = er.ReadUInt32();
            h.stringTableOffset = er.ReadUInt32();
        }

        private static void readJnt1Entry(EndianBinaryReader er, JntEntry e)
        {
            e.unknown = er.ReadUInt16();
            e.unknown3 = er.ReadByte();
            e.pad = er.ReadByte();

            e.sx = er.ReadInt32(); e.sy = er.ReadInt32(); e.sz = er.ReadInt32();
            Helper.toFLOAT(e.sx); Helper.toFLOAT(e.sy); Helper.toFLOAT(e.sz);
            e.rx = er.ReadInt16(); e.ry = er.ReadInt16(); e.rz = er.ReadInt16();
            Helper.toSHORT((ushort)e.rx); Helper.toSHORT((ushort)e.ry); Helper.toSHORT((ushort)e.rz);
            e.pad2 = er.ReadUInt16();
            e.tx = er.ReadInt32(); e.ty = er.ReadInt32(); e.tz = er.ReadInt32();
            Helper.toDWORD((uint)e.tx); Helper.toDWORD((uint)e.ty); Helper.toDWORD((uint)e.tz);
            e.unknown2 = er.ReadSingle();

            int j;
            for (j = 0; j < 3; ++j)
            {
                e.bbMin[j] = er.ReadInt32();
                Helper.toFLOAT(e.bbMin[j]);
            }
            for (j = 0; j < 3; ++j)
            {
                e.bbMax[j] = er.ReadInt32();
                Helper.toFLOAT(e.bbMax[j]);
            }
        }

        public static void dumpJnt1(EndianBinaryReader er, Jnt1 dst)
        {
            int jnt1Offset = (int)er.BaseStream.Length;

            //read header
            Jnt1Header h = new Jnt1Header();
            readJnt1Header(er, h);

            //read stringtable
            List<string> stringtable = new List<string>();
            Helper.readStringtable(jnt1Offset + (int)h.stringTableOffset, er, stringtable);

            if (stringtable.Count != h.count)
            {
                //warn("jnt1: number of strings doesn't match number of joints");
            }

            //read joints
            er.BaseStream.Position = jnt1Offset + h.jntEntryOffset;

            dst.frames.AddRange(new Frame[h.count]);
            dst.matrices.AddRange(new LibNDSFormats.NSBMD.MTX44[h.count]);
            dst.isMatrixValid.AddRange(new bool[h.count]);
            for (int i = 0; i < h.count; i++)
            {
                dst.isMatrixValid[i] = false;
            }
            for (int i = 0; i < h.count; ++i)
            {
                JntEntry e = new JntEntry();
                readJnt1Entry(er, e);

                Frame f = dst.frames[i];
                f.sx = e.sx;
                f.sy = e.sy;
                f.sz = e.sz;
                f.rx = e.rx / 32768.0f * 180;
                f.ry = e.ry / 32768.0f * 180;
                f.rz = e.rz / 32768.0f * 180;
                f.t = new OpenTK.Vector3(e.tx, e.ty, e.tz);
                if (i < stringtable.Count) //should always be true
                    f.name = stringtable[i];

                f.unknown = e.unknown;
                f.unknown3 = e.unknown3;
                f.bbMin = new OpenTK.Vector3(e.bbMin[0], e.bbMin[1], e.bbMin[2]);
                f.bbMax = new OpenTK.Vector3(e.bbMax[0], e.bbMax[1], e.bbMax[2]);
            }
        }

    }
}
